系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念
// Switch to scheduler. Must hold only p->lock
// and have changed proc->state. Saves and restores
// intena because intena is a property of this
// kernel thread, not this CPU. It should
// be proc->intena and proc->noff, but that would
// break in the few places where a lock is held but
// there's no process.
void
sched(void)
{
int intena;
struct proc *p = myproc();
if(!holding(&p->lock))
panic("sched p->lock");
if(mycpu()->noff != 1)
panic("sched locks");
if(p->state == RUNNING)
panic("sched RUNNING");
if(intr_get())
panic("sched interruptible");
intena = mycpu()->intena;
swtch(&p->context, &mycpu()->context);
mycpu()->intena = intena;
}
scheduler thread 的 context。 if(!holding(&p->lock))
scheduler thread
if(mycpu()->noff != 1)
if(p->state == RUNNING)
if(intr_get())
intena = mycpu()->intena;
swtch(&p->context, &mycpu()->context);
context switch 到該 CPU 的 scheduler thread。mycpu()->intena = intena;
在這邊,對於 p->lock 的使用也適合討論一下,因為這裡稀有的會讓 process-A 上鎖,並讓其他 thread ( CPU-scheduler-thread ) 解鎖。
// Give up the CPU for one scheduling round.
void
yield(void)
{
struct proc *p = myproc();
acquire(&p->lock);
p->state = RUNNABLE;
sched();
release(&p->lock);
}
p->state = RUNNABLE;
sched()
release(&p->lock);
// Per-CPU process scheduler.
// Each CPU calls scheduler() after setting itself up.
// Scheduler never returns. It loops, doing:
// - choose a process to run.
// - swtch to start running that process.
// - eventually that process transfers control
// via swtch back to the scheduler.
void
scheduler(void)
{
struct proc *p;
struct cpu *c = mycpu();
c->proc = 0;
c->proc = 0 : 代表目前 CPU 沒有運行任何的 process,目前運行的是 CPU-scheduler-thread
假如在 CPU-scheduler-thread 裡面發生了 interrupt ( e.g. timer interrupt ),interrupt handler 會看到 myproc() == 0,並跳過 preemption ( yield )。
for(;;){
// The most recent process to run may have had interrupts
// turned off; enable them to avoid a deadlock if all
// processes are waiting. Then turn them back off
// to avoid a possible race between an interrupt
// and wfi.
intr_on();
intr_off();
intr_on ) 爾後又馬上把 interrupt 給關掉 ( intr_off ),這兩個 function 是為了不同的目的
Lost Wake-up Race Conditioninterrupt 而醒來的話,需要把 interrupt 打開,並讓 CPU 去處理 pending 的 interrupt沒有做這一件事,會發生什麼事情呢 ?。
acquire function ),並且 disable interrupt。所有 process 都關掉了 interrupt,這就變成,這個系統永遠也不會有 RUNNABLE 的 process,整個系統都會卡住,變成 deadlock !!!Lost Wake-up Race Condition )
intr_off,並保持 interrupt 開啟的話,會發生什麼事情呢 ?
RUNNABLE process ( found == 0 )錯過了這個 wake up ( Lost Wake-up )
intr_off 關掉中斷,我們之後用 for-loop 掃過全部 process 的時候,就不會去處理中斷 ( e.g. 去 interrupt service routine 處理 disk-reading )。wfi 有一個特性是,就算 interrupt 被 globally disabled 了 ( 用 intr_off 把 SSTATUS_SIE 歸 0 ),只要有 interrupt 正在 pending,CPU 仍舊會從 wfi 甦醒。for(;;) 的下一輪,碰上了 intr_on (link) int found = 0;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state == RUNNABLE) {
// Switch to chosen process. It is the process's job
// to release its lock and then reacquire it
// before jumping back to us.
p->state = RUNNING;
c->proc = p;
swtch(&c->context, &p->context);
p->state = RUNNING;
c->proc = p;
swtch(&c->context, &p->context);
scheduler-thread 切換到 process 的 context。 // Process is done running for now.
// It should have changed its p->state before coming back.
c->proc = 0;
found = 1;
}
release(&p->lock);
}
c->proc = 0;
found = 1;
release(&p->lock);
if(found == 0) {
// nothing to run; stop running on this core until an interrupt.
asm volatile("wfi");
}
}
}
假如這一輪沒有找到任何可以運行的 process ( 沒有 RUNNABLE 的 process ),就用 wfi 進入睡眠。
// Must be called with interrupts disabled,
// to prevent race with process being moved
// to a different CPU.
int
cpuid()
{
int id = r_tp();
return id;
}
start()(link) 放到 tp register 裡面。// Return this CPU's cpu struct.
// Interrupts must be disabled.
struct cpu*
mycpu(void)
{
int id = cpuid();
struct cpu *c = &cpus[id];
return c;
}
// Return the current struct proc *, or zero if none.
struct proc*
myproc(void)
{
push_off();
struct cpu *c = mycpu();
struct proc *p = c->proc;
pop_off();
return p;
}
CPU-scheduler-thread,沒有運行任何的 process,則這個 function 會回傳 0。push_off 來關閉 interrupt,免得執行到一半,該 CPU 實際執行的 process 已經改變了,然後回傳錯的資訊。一個 process 的離去,不能單單只靠自己,而是需要靠 parent-process 來把資源給釋放掉。所以每個 process 必定會有一個 parent。
TODO : … 可能會有個特例,需要研究一下 initproc。
為什麼要靠別的 process 來釋放掉呢 ?
假如不靠別人的話,該怎麼釋放資源呢 ? 自己把自己的 text section release 掉嗎 ? 可是自己這個 process 的程式碼仍在運行,仍在使用著 text section,要怎麼把 text section 釋放掉呢 ?
到最後,最直覺的方式,似乎仍舊是要讓 parent process 來幫自己釋放掉資源。
// Exit the current process. Does not return.
// An exited process remains in the zombie state
// until its parent calls wait().
void
kexit(int status)
{
status : exit code,會把它傳遞給 parent process。
struct proc *p = myproc();
拿出當前正在執行的 process 的 struct-proc。
if(p == initproc)
panic("init exiting");
wait orphan processes。 // Close all open files.
for(int fd = 0; fd < NOFILE; fd++){
if(p->ofile[fd]){
struct file *f = p->ofile[fd];
fileclose(f);
p->ofile[fd] = 0;
}
}
struct-file *,只要不是 0 的話,都代表該 process 所開啟的檔案。 begin_op();
iput(p->cwd);
end_op();
p->cwd = 0;
begin_op 以及 end_op 來圈出一段 transaction。 acquire(&wait_lock);
// Give any children to init.
reparent(p);
// Parent might be sleeping in wait().
wakeup(p->parent);
reparent
wait function )。p 的 children process 找到新的 parent-process。在這裡,會將所有 children-process 託付給一個特殊的 process : initproc ( PID == 1 )。 acquire(&p->lock);
p->xstate = status;
p->state = ZOMBIE;
release(&wait_lock);
// Jump into the scheduler, never to return.
sched();
panic("zombie exit");
}
p->xstate = status
// Kill the process with the given pid.
// The victim won't exit until it tries to return
// to user space (see usertrap() in trap.c).
int
kkill(int pid)
for(p = proc; p < &proc[NPROC]; p++){
一個 for-loop 去遍尋所有的 process。
acquire(&p->lock);
if(p->pid == pid){
p->killed = 1;
if(p->state == SLEEPING){
// Wake process from sleep().
p->state = RUNNABLE;
}
release(&p->lock);
return 0;
}
release(&p->lock);
}
return -1;
}
kkill 執行失敗,回傳 -1。// Wait for a child process to exit and return its pid.
// Return -1 if this process has no children.
int
kwait(uint64 addr)
{
struct proc *pp;
int havekids, pid;
struct proc *p = myproc();
acquire(&wait_lock);
因為等一下要處理 parent-children process relationship ( e.g. 遍尋 process table 去尋找當前 process 的 children process ),所以需要先拿 global-wait_lock。
for(;;){
// Scan through table looking for exited children.
進入一個無窮迴圈,退出的條件為
havekids = 0;
for(pp = proc; pp < &proc[NPROC]; pp++){
嘗試去遍尋每一個 process,試圖找到所有的 children-processes
if(pp->parent == p){
// make sure the child isn't still in exit() or swtch().
acquire(&pp->lock);
havekids = 1;
if(pp->state == ZOMBIE){
// Found one.
pid = pp->pid;
if(addr != 0 && copyout(p->pagetable, addr, (char *)&pp->xstate,
sizeof(pp->xstate)) < 0) {
release(&pp->lock);
release(&wait_lock);
return -1;
}
freeproc(pp);
release(&pp->lock);
release(&wait_lock);
return pid;
}
release(&pp->lock);
}
}
UNUSED,可供被再次 allocate。 // No point waiting if we don't have any children.
if(!havekids || killed(p)){
release(&wait_lock);
return -1;
}
假如該 process 沒有任何 children,或是已經被 kill 掉的話,就直接宣道 kwait 失敗,回傳 -1。
// Wait for a child to exit.
sleep(p, &wait_lock); //DOC: wait-sleep
}
}
kexit 的時候,會用 wakeup(p->parent); 把 parent process 喚醒。